package compilador.semantico;

import compilador.lexico.TipoToken;
import java.util.LinkedList;

/**
 * Classe do analisador semântico. Mantém uma pilha de tabelas de símbolos para
 * inserções e consultas, a fim de que se possa fazer as verificações de tipos,
 * declarações, etc conforme o escopo atual.
 *
 * @author Carlos H V Pinto
 * @author Lucas S Bueno
 */
public class AnalisadorSemantico {

    /**
     * Lista encadeada que se comporta como uma pilha de tabelas. O seu primeiro
     * elemento representa o escopo de aninhamento mais próximo.
     */
    private LinkedList<TabelaSimbolos> pilhaTabelas;
    private int qtdeErros; //contabiliza quantos erros semânticos ocorreram
    private boolean funcao; //controle para verificar comandos retorne não permitidos

    /**
     * Construtor. Inicializa o analisador semântico.
     */
    public AnalisadorSemantico() {
        pilhaTabelas = new LinkedList<TabelaSimbolos>();
        qtdeErros = 0;
        funcao = false;
    }

    public int getQtdeErros() {
        return qtdeErros;
    }

    public boolean isFuncao() {
        return funcao;
    }

    public void setFuncao(boolean funcao) {
        this.funcao = funcao;
    }   

    /**
     * Insere um novo escopo aninhado, adicionando uma nova tabela de símbolos à
     * "pilha" de tabelas.
     */
    public void insereEscopo() {
        // equivalente a addFirst()
        pilhaTabelas.push(new TabelaSimbolos());
    }

    /**
     * Remove o escopo de aninhamento mais próximo, removendo sua tabela de
     * símbolos da "pilha" de tabelas.
     */
    public void removeEscopo() {
        // equivalente a removeFirst()
        pilhaTabelas.pop();
    }

    /**
     * Obtém a tabela de símbolos do escopo atual (aninhamento mais próximo).
     */
    public TabelaSimbolos escopoAtual() {
        // equivalente a getFirst()
        return pilhaTabelas.peek();
    }

    /**
     * Tenta inserir um símbolo dentro do escopo atual. Para isso, primero
     * verifica se o símbolo a ser inserido não existe nesse escopo.
     */
    public boolean insereSimbolo(String chave, PropriedadesSimboloBasico props) {
        return escopoAtual().insereSimbolo(chave, props);
    }
    
    /**
     * Verifica se o símbolo especificado pela chave pertence à algum escopo. A
     * busca é feita do escopo mais próximo ao mais distante. Retorna o símbolo
     * caso seja encontrado ou null caso contrário.
     */
    public PropriedadesSimboloBasico buscaSimbolo(String chave) {
        //percorre a "pilha" de tabelas do escopo mais próximo ao mais distante
        for (TabelaSimbolos umaTabela : pilhaTabelas) {
            PropriedadesSimboloBasico simbolo = umaTabela.buscaSimbolo(chave);
            
            if (simbolo != null) {
                return simbolo;
            }
        }
        return null;
    }

    /**
     * Identifica e exibe a mensagem de erro para um erro semântico ocorrido
     * durante a análise. Também manipula a variável de quantidade de erros.
     */
    public void erro(TipoErroSemantico tipoErro, int linha, String cadeia) {
        qtdeErros++;
        switch (tipoErro) {
            case IdentificadorJaDeclarado:
                System.out.println("Linha " + linha + ": identificador " + cadeia + " ja declarado anteriormente");
                break;
            case TipoNaoDeclarado:
                System.out.println("Linha " + linha + ": tipo " + cadeia + " nao declarado");
                break;
            case IdentificadorNaoDeclarado:
                System.out.println("Linha " + linha + ": identificador " + cadeia + " nao declarado");
                break;
            case IncompatibilidadeParametros:
                System.out.println("Linha " + linha + ": incompatibilidade de parametros na chamada de " + cadeia);
                break;
            case AtribuicaoNaoCompativel:
                System.out.println("Linha " + linha + ": atribuicao nao compativel para " + cadeia);
                break;
            case ComandoRetorneNaoPermitido:
                System.out.println("Linha " + linha + ": comando retorne nao permitido nesse escopo");
                break;
            default:
                throw new RuntimeException("Erro semantico desconhecido");
        }
    }

    /**
     * Método auxiliar para traduzir um tipo de token em um nome de tipo de dado
     * da linguagem (tipo básico ou definido pelo usuário).
     */
    public String traduzTipoBasico(TipoToken tipoToken) {
        switch (tipoToken) {
            case NumeroInteiro:
            case PR_Inteiro:
                return TipoBasico.INTEIRO;
            case CadeiaLiteral:
            case PR_Literal:
                return TipoBasico.LITERAL;
            case PR_Falso:
            case PR_Verdadeiro:
            case PR_Logico:
                return TipoBasico.LOGICO;
            case NumeroReal:
            case PR_Real:
                return TipoBasico.REAL;
            default:
                return null;
        }
    }
}
